﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MARC.HI.EHRS.SVC.Messaging.FHIR.Resources;
using System.ServiceModel.Web;
using System.Reflection;
using MARC.HI.EHRS.SVC.Messaging.FHIR.Handlers;
using MARC.HI.EHRS.SVC.Messaging.FHIR.WcfCore;
using System.ServiceModel;
using System.Diagnostics;
using System.ServiceModel.Syndication;

namespace MARC.HI.EHRS.SVC.Messaging.FHIR.Util
{
    /// <summary>
    /// Conformance generation utility
    /// </summary>
    public static class ConformanceUtil
    {

        // Conformance
        private static Conformance s_conformance = null;

        // Sync lock
        private static Object s_syncLock = new object();

        /// <summary>
        /// Get conformance statement
        /// </summary>
        public static Conformance GetConformanceStatement()
        {

            if (s_conformance == null)
                lock (s_syncLock)
                {
                    BuildConformanceStatement();
                    WebOperationContext.Current.OutgoingResponse.LastModified = DateTime.Now;
                }

            return s_conformance;

        }

        /// <summary>
        /// Build conformance statement
        /// </summary>
        private static void BuildConformanceStatement()
        {
            s_conformance = new Conformance();
            TraceListener[] currentListeners = new TraceListener[Trace.Listeners.Count];

            try
            {
                // No output of any exceptions
                Trace.Listeners.CopyTo(currentListeners, 0);
                Trace.Listeners.Clear();

                Assembly entryAssembly = Assembly.GetEntryAssembly();

                // First assign the basic attributes
                s_conformance.VersionId = s_conformance.Id = entryAssembly.GetName().Version.ToString();
                s_conformance.Software = SoftwareDefinition.FromAssemblyInfo();
                s_conformance.AcceptUnknown = false;
                s_conformance.Date = DateTime.Now;
                s_conformance.Timestamp = DateTime.Now;
                s_conformance.Description = "Automatically generated by ServiceCore FHIR framework";
                s_conformance.Experimental = true;
                s_conformance.FhirVersion = "0.80";
                s_conformance.Format.Add(new DataTypes.PrimitiveCode<string>("xml"));
                s_conformance.Format.Add(new DataTypes.PrimitiveCode<string>("json"));

                s_conformance.Implementation = new ImplementationDefinition()
                {
                    Url = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri,
                    Description = entryAssembly.GetCustomAttribute<AssemblyDescriptionAttribute>().Description
                };
                s_conformance.Name = "SVC-CORE FHIR";
                s_conformance.Publisher = entryAssembly.GetCustomAttribute<AssemblyCompanyAttribute>().Company;
                s_conformance.Status = new DataTypes.PrimitiveCode<string>("draft");

                // Generate the rest description
                // TODO: Reflect the current WCF context and get all the types of communication supported
                s_conformance.Rest.Add(CreateRestDefinition());
                s_conformance.Text = null;
            }
            finally
            {
                Trace.Listeners.AddRange(currentListeners);
            }
        }

        /// <summary>
        /// Create a REST service definition
        /// </summary>
        private static RestDefinition CreateRestDefinition()
        {
            var retVal = new RestDefinition();

            retVal.Mode = new DataTypes.PrimitiveCode<string>("server");
            retVal.Documentation = "Default WCF REST Handler";

            // Now comes the fun part ... get all of the resource handlers loaded
            var behaviorInstance = new FhirServiceBehavior();

            foreach (var hdlr in FhirResourceHandlerUtil.ResourceHandlers)
            {
                // Create the resource entry
                ResourceDefinition defn = new ResourceDefinition();
                defn.Type = new DataTypes.PrimitiveCode<string>(hdlr.ResourceName);
                
                // Find all applicable profiles
                var appliedProfiles = ProfileUtil.GetProfiles().FirstOrDefault(o => o.Structure.Exists(s => s.Type == hdlr.ResourceName) && o.Name != "svccore" );
                if (appliedProfiles != null)
                    appliedProfiles = ProfileUtil.GetProfiles().FirstOrDefault(o => o.Structure.Exists(s => s.Type == hdlr.ResourceName));
                if(appliedProfiles != null)
                {
                    var ap = appliedProfiles;
                    Resource<Profile> profileRef = Resource<Profile>.CreateResourceReference(ap, WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri);
                    defn.Profile = profileRef;
                    if(ap.Structure.Find(s => s.Type == hdlr.ResourceName).SearchParams != null)
                        defn.SearchParams.AddRange(ap.Structure.Find(s => s.Type.ToString() == hdlr.ResourceName).SearchParams);
                    foreach(var sp in defn.SearchParams)
                        if(sp.Definition == null)
                            sp.Definition = new Uri(profileRef.Reference.ToString() + "#" + sp.Name);
                }

                // Supported operations
                foreach (var mi in typeof(IFhirServiceContract).GetMethods().Where(m => m.GetCustomAttribute<OperationContractAttribute>() != null))
                {
                    if (mi.GetCustomAttribute<OperationContractAttribute>() == null ||
                        String.IsNullOrEmpty(mi.GetCustomAttribute<OperationContractAttribute>().Name))
                        continue;

                    var parms = mi.GetParameters();
                    object[] miParms = new object[parms.Length];
                    if(parms[0].ParameterType == typeof(String)) // Resource type?
                        miParms[0] = hdlr.ResourceName;

                    // Check for support by calling the method and observing the result. If NotImplemented or NotSupported
                    // then it is not supported.
                    bool supported = false;
                    try
                    {
                        mi.Invoke(behaviorInstance, miParms); // try to call
                        supported = true;
                    }
                    catch (TargetInvocationException e)
                    {
                        if (e.InnerException is NotImplementedException ||
                            e.InnerException is NotSupportedException)
                            ;
                        else if (e.InnerException is WebFaultException<OperationOutcome>)
                        {
                            var ex = e.InnerException as WebFaultException<OperationOutcome>;
                            supported = ex.StatusCode != System.Net.HttpStatusCode.NotImplemented && ex.StatusCode != System.Net.HttpStatusCode.MethodNotAllowed;
                        }
                        else if (e.InnerException is WebFaultException<Atom10FeedFormatter>)
                        {
                            var ex = e.InnerException as WebFaultException<Atom10FeedFormatter>;
                            supported = ex.StatusCode != System.Net.HttpStatusCode.NotImplemented && ex.StatusCode != System.Net.HttpStatusCode.MethodNotAllowed;
                        }
                        else
                            supported = true;
                    }
                    catch (Exception)
                    {
                        supported = true;
                    }

                    // Supported
                    if(supported)
                        defn.Operation.Add(new OperationDefinition()
                        {
                            Type = new DataTypes.PrimitiveCode<string>(mi.GetCustomAttribute<OperationContractAttribute>().Name)
                        });

                }

                retVal.Resource.Add(defn);

            }

            return retVal;
        }
    }
}
